package com.xhq.webmvc.servlet;

import com.alibaba.fastjson.JSON;
import com.xhq.commons.util.ConfigUtils;
import com.xhq.commons.util.PropertiesHandler;
import com.xhq.webmvc.annotation.RequestBean;
import com.xhq.webmvc.annotation.RequestMapping;
import com.xhq.webmvc.annotation.ResponseJson;
import com.xhq.webmvc.constant.Constants;
import com.xhq.webmvc.exception.InitActionException;
import com.xhq.webmvc.model.ActionModel;
import com.xhq.webmvc.model.JSONResponse;
import org.apache.commons.beanutils.BeanUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.HashMap;
import java.util.Map;

/**
 *   核心Servlet 负责加载Action,分发请求
 */

public class DispatcherServlet extends HttpServlet{
    private static Logger log = LoggerFactory.getLogger(DispatcherServlet.class);  //日志记录
    private static Map<String,ActionModel> actionPool; //存放所有action的容器
    private static String viewPrefix ;   //视图前缀名
    private static String viewSuffix ;  //视图后缀名
    /**
     * 初始化操作
     *  1. 获取配置文件中配置的需扫描的包名
     *  2. 根据包名扫描注解，获取URL，创建Bean实例
     * @throws ServletException
     */
    @Override
    public void init() throws ServletException {
        log.info("开始加载action");
        actionPool = new HashMap<>(); //初始化容器
        //读取配置文件中的包路径（扫描该路径下的所有Action）
        PropertiesHandler propsHandler = new PropertiesHandler(ConfigUtils.readValue(Constants.CONFIG_LOCATION));
        String scanPackage  = propsHandler.readValue(Constants.SCAN_PACKAGE).replace(".", "/");
        String path = Thread.currentThread().getContextClassLoader().getResource(scanPackage).getFile(); //取得绝对路径
        try {
            //开始扫描加载包下的Action
            scanPackage(path,actionPool);
        } catch (Exception e) {
            e.printStackTrace();
            log.error("Action加载异常");
        }
        log.info("加载action完成");

        viewPrefix = propsHandler.readValue(Constants.VIEW_PREFIX);  //设置文件前缀名称
        viewSuffix = propsHandler.readValue(Constants.VIEW_SUFFIX);  //设置文件后缀名称
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //设置字符集编码
        req.setCharacterEncoding(Constants.DEFAULT_CHARSET);
        resp.setCharacterEncoding(Constants.DEFAULT_CHARSET);

        //判断请求是否为ajax
        boolean isAjax = ("XMLHttpRequest".equals(req.getHeader("X-Requested-with"))) ? true : false;
        String requestURI = req.getRequestURI();  //获取当前请求路径

        //从actionz资源池中获取对应的ActionModel
        ActionModel actionModel = actionPool.get(requestURI);

        if (actionModel!=null) {//如果有资源与之对应，则分发执行
            try {
                Method method = actionModel.getMethod();
                Parameter[] parameters = actionModel.getParameters();  //获取方法形参列表
                Map<String, String[]> parameterMap = req.getParameterMap();  //获取请求参数map
                Object[] realParams = new Object[parameters.length];  //实参列表
                for(int i = 0; i < parameters.length; i ++ ) {
                    Parameter parameter = parameters[i];
                    String name = parameter.getName();
                    Class<?> paramTypeClass = parameter.getType();   //参数的类型对象

                    if(paramTypeClass == HttpServletRequest.class) {  // 注入request对象
                        realParams[i] = req;
                    } else if(paramTypeClass == HttpServletResponse.class){ // 注入response对象
                        realParams[i] = resp;
                    }else if(parameter.isAnnotationPresent(RequestBean.class)){  // 注入实体Bean
                        Object o = paramTypeClass.newInstance();
                        BeanUtils.populate(o,parameterMap);   //用map 填充Bean
                        realParams[i] = o;
                    }else {   //注入普通属性
                        String parameterValue = req.getParameter(name);
                        realParams[i] = converter(parameterValue, paramTypeClass);
                    }
                }

                //执行方法，接受方法返回值
                Object rs = method.invoke(actionModel.getAction(),realParams);
                //判断是否要返回Json
                boolean isJson = method.isAnnotationPresent(ResponseJson.class);

                //根据返回的结果类型，返回值进行处理
                if (rs instanceof String && !isJson) { //如果是String类型返回值，并且不是Json请求就跳转页面
                    resp.setContentType("text/html");
                    String path =  (String) rs;
                    if (path.startsWith(Constants.REDIRECT_PREFIX)) { //重定向
                        resp.sendRedirect(req.getContextPath() + path.substring(Constants.REDIRECT_PREFIX.length() ));
                    }else if (path.startsWith(Constants.FORWARD_PREFIX)){  //转发到另一个服务器资源
                        req.getRequestDispatcher( req.getContextPath() + path.substring(Constants.FORWARD_PREFIX.length())).forward(req, resp);
                    }else {//转发到 视图资源（jsp）
                        req.getRequestDispatcher(viewPrefix + path + viewSuffix).forward(req, resp);
                    }
                }else if(isJson) {  //如果要返回Json
                    writeJson(rs, resp);
                }else {   //即不是String类型，也不返回Json
                    resp.sendError(404);  //发送404异常
                }
            } catch (Exception e) {
                //报500错误了
                e.printStackTrace();
                if(isAjax){ //如果是ajax请求则返回
                    JSONResponse jsonModel = new JSONResponse(JSONResponse.CODE_500,"服务器错误");
                    writeJson(jsonModel,resp);
                }else {
                    resp.sendError(500);  //发送404异常
                }
            }
        }else {//如果没有资源与之对应则返回相应数据
            if(isAjax){ //如果是ajax请求则返回
                JSONResponse jsonModel = new JSONResponse(JSONResponse.CODE_404,"资源未找到");
                writeJson(jsonModel,resp);
            }else {
                resp.sendError(404);  //发送404异常
            }

        }
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doGet(req, resp);
    }


    /**
     * 递归扫描指定包下的action
     * @param
     */
    private void scanPackage(String path, Map<String, ActionModel> actionPool) throws ClassNotFoundException, InstantiationException, InitActionException, IllegalAccessException {
        File file = new File(path);
        if (file.isDirectory()) { //如果是文件夹，获取文件夹下的文件
            File[] files = file.listFiles();
            for (File temp : files) {
                scanPackage(temp.getPath(),actionPool);
            }
        } else { //如果是文件
            loadAction(file);
        }

    }

    /**
     * 加载action 中所有可被请求的 方法
     */
    private void loadAction (File file) throws ClassNotFoundException, IllegalAccessException, InstantiationException, InitActionException {
        String path = file.getPath();
        String className = path.substring(path.indexOf("com"), path.lastIndexOf(".")).replace("\\", ".");
            //加载类
            Class clazz = Class.forName(className);
            //获取类注解
            String classUrl = "";

            if (clazz.isAnnotationPresent(RequestMapping.class)) {
                classUrl = ((RequestMapping)clazz.getAnnotation(RequestMapping.class)).value();
            }
            //获取方法上注解
            Method[] methods = clazz.getMethods();
            Object action = clazz.newInstance();
            for (Method method : methods) {
                if (method.isAnnotationPresent(RequestMapping.class)) { //是否有 @RequestMapping 注解
                    RequestMapping annotation = method.getAnnotation(RequestMapping.class);
                    String mp = (classUrl + annotation.value()); //访问路径
                    if (actionPool.containsKey(mp)) {
                        log.info("存在多个相同映射：{}",mp);
                        throw new InitActionException("已存在URL：" + mp);
                    } else {
                        Parameter[] parameters = method.getParameters(); //该方法的形参列表数组
                        ActionModel actionModel = new ActionModel(method,action,parameters);
                        actionPool.put(mp, actionModel);
                        log.info("已加载：{}",mp);
                    }
                }
            }

    }

    /**
     * 输出json
     * @param jsonModel
     * @param resp
     * @throws IOException
     */
    private void writeJson(Object jsonModel, HttpServletResponse resp) throws IOException {
        resp.setContentType("text/json");
        PrintWriter writer = resp.getWriter();
        String json = JSON.toJSONString(jsonModel);
        writer.write(json);
        writer.close();
    }

    /**
     * 参数类型转换器
     * @param parameterValue
     * @param paramTypeClass
     * @return
     */
    public Object converter(String parameterValue, Class paramTypeClass){
        Object rtVal = null;
        if(null != parameterValue) {
            if(paramTypeClass == Integer.class || paramTypeClass == int.class) {
                rtVal = Integer.valueOf(parameterValue);
            }else  if(paramTypeClass == Short.class || paramTypeClass == short.class) {
                rtVal = Short.valueOf(parameterValue);
            }else  if(paramTypeClass == Byte.class || paramTypeClass == byte.class) {
                rtVal = Byte.valueOf(parameterValue);
            }else  if(paramTypeClass == Long.class || paramTypeClass == long.class) {
                rtVal = Long.valueOf(parameterValue);
            }else  if(paramTypeClass == Double.class || paramTypeClass == double.class) {
                rtVal = Double.valueOf(parameterValue);
            }else  if(paramTypeClass == Float.class || paramTypeClass == float.class) {
                rtVal = Float.valueOf(parameterValue);
            }else  if(paramTypeClass == Boolean.class || paramTypeClass == boolean.class) {
                rtVal = Boolean.valueOf(parameterValue);
            }else  if(paramTypeClass == Character.class || paramTypeClass == char.class) {
                rtVal = parameterValue.charAt(0);
            }else {
                rtVal = parameterValue;
            }
        }

        return rtVal;
    }
}
